package com.itjin.health.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.itjin.health.domain.dto.timeslot.AvailableSlotPublishRequest;
import com.itjin.health.domain.dto.timeslot.LocationAddRequest;
import com.itjin.health.domain.dto.timeslot.TimeSlotAddRequest;
import com.itjin.health.domain.entity.AvailableSlot;
import com.itjin.health.domain.entity.Location;
import com.itjin.health.domain.entity.TimeSlot;
import com.itjin.health.exception.BusinessException;
import com.itjin.health.exception.ErrorCode;
import com.itjin.health.mapper.AvailableSlotMapper;
import com.itjin.health.mapper.LocationMapper;
import com.itjin.health.mapper.TimeSlotMapper;
import com.itjin.health.service.TimeAndLocationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.WeekFields;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 时间和地点管理服务实现类
 */
@Service
@Slf4j
public class TimeAndLocationServiceImpl implements TimeAndLocationService {

  @Resource
  private TimeSlotMapper timeSlotMapper;

  @Resource
  private LocationMapper locationMapper;

  @Resource
  private AvailableSlotMapper availableSlotMapper;

  @Override
  public TimeSlot addTimeSlot(TimeSlotAddRequest timeSlotAddRequest) {
    // 验证输入
    if (timeSlotAddRequest == null || timeSlotAddRequest.getDayOfWeek() == null ||
        timeSlotAddRequest.getStartTime() == null || timeSlotAddRequest.getEndTime() == null) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不完整");
    }

    // 验证星期几格式
    String dayOfWeek = timeSlotAddRequest.getDayOfWeek();
    String[] validDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
    boolean isValidDay = Arrays.asList(validDays).contains(dayOfWeek);
    if (!isValidDay) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "星期几格式不正确");
    }

    // 验证时间段
    if (timeSlotAddRequest.getStartTime().isAfter(timeSlotAddRequest.getEndTime())) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "开始时间不能晚于结束时间");
    }

    // 检查是否已存在相同时间段
    QueryWrapper<TimeSlot> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("DayOfWeek", dayOfWeek)
        .eq("StartTime", timeSlotAddRequest.getStartTime())
        .eq("EndTime", timeSlotAddRequest.getEndTime());
    if (timeSlotMapper.selectCount(queryWrapper) > 0) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "该时间段已存在");
    }

    // 创建并保存时间段
    TimeSlot timeSlot = new TimeSlot();
    BeanUtils.copyProperties(timeSlotAddRequest, timeSlot);
    timeSlotMapper.insert(timeSlot);

    return timeSlot;
  }

  @Override
  public List<TimeSlot> listTimeSlots() {
    // 查询所有时间段，按星期几和开始时间排序
    QueryWrapper<TimeSlot> queryWrapper = new QueryWrapper<>();
    queryWrapper
        .orderByAsc("FIELD(DayOfWeek, 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday')")
        .orderByAsc("StartTime");
    return timeSlotMapper.selectList(queryWrapper);
  }

  @Override
  public Location addLocation(LocationAddRequest locationAddRequest) {
    // 验证输入
    if (locationAddRequest == null || locationAddRequest.getLocationName() == null
        || locationAddRequest.getLocationName().isEmpty()) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "地点名称不能为空");
    }

    // 检查是否已存在相同名称的地点
    QueryWrapper<Location> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("LocationName", locationAddRequest.getLocationName());
    if (locationMapper.selectCount(queryWrapper) > 0) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "该地点名称已存在");
    }

    // 创建并保存地点
    Location location = new Location();
    BeanUtils.copyProperties(locationAddRequest, location);
    locationMapper.insert(location);

    return location;
  }

  @Override
  public List<Location> listLocations() {
    // 查询所有地点，按ID排序
    QueryWrapper<Location> queryWrapper = new QueryWrapper<>();
    queryWrapper.orderByAsc("locationid");
    return locationMapper.selectList(queryWrapper);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void publishAvailableSlots(AvailableSlotPublishRequest publishRequest, Integer adminId) {
    // 验证输入
    if (publishRequest == null || publishRequest.getPublishDate() == null
        || publishRequest.getAssessmentType() == null) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不完整");
    }

    String assessmentType = publishRequest.getAssessmentType();
    if (!"线上".equals(assessmentType) && !"线下".equals(assessmentType)) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "评估类型格式不正确");
    }

    // 获取发布年份和周数
    LocalDate publishDate = publishRequest.getPublishDate();
    int year = publishDate.getYear();
    int week = publishDate.get(WeekFields.of(DayOfWeek.MONDAY, 1).weekOfYear());

    // 删除该周已有的发布（覆盖更新）
    QueryWrapper<AvailableSlot> deleteWrapper = new QueryWrapper<>();
    deleteWrapper.eq("AdminID", adminId)
        .eq("PublishYear", year)
        .eq("PublishWeek", week)
        .eq("AssessmentType", assessmentType);
    availableSlotMapper.delete(deleteWrapper);

    // 根据评估类型处理发布内容
    if ("线上".equals(assessmentType)) {
      // 线上评估
      if (publishRequest.getOnlineSlotIds() == null || publishRequest.getOnlineSlotIds().isEmpty()) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR, "线上时间段不能为空");
      }

      // 验证时间段ID是否有效
      List<Integer> slotIds = publishRequest.getOnlineSlotIds();
      for (Integer slotId : slotIds) {
        TimeSlot timeSlot = timeSlotMapper.selectById(slotId);
        if (timeSlot == null) {
          throw new BusinessException(ErrorCode.PARAMS_ERROR, "时间段ID无效: " + slotId);
        }

        // 为每个时间段创建可用时间配置
        AvailableSlot availableSlot = new AvailableSlot();
        availableSlot.setAdminId(adminId);
        availableSlot.setSlotId(slotId);
        availableSlot.setLocationId(null); // 线上评估无需地点
        availableSlot.setAssessmentType(assessmentType);
        availableSlot.setAvailable(true);
        availableSlot.setPublishDate(publishDate);
        availableSlot.setPublishYear(year);
        availableSlot.setPublishWeek(week);
        availableSlotMapper.insert(availableSlot);
      }
    } else {
      // 线下评估
      if (publishRequest.getOfflineConfigs() == null || publishRequest.getOfflineConfigs().isEmpty()) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR, "线下配置不能为空");
      }

      // 验证时间段ID和地点ID是否有效，并创建可用时间配置
      List<AvailableSlotPublishRequest.OfflineSlotConfig> offlineConfigs = publishRequest.getOfflineConfigs();
      for (AvailableSlotPublishRequest.OfflineSlotConfig config : offlineConfigs) {
        Integer slotId = config.getSlotId();
        Integer locationId = config.getLocationId();

        // 验证时间段
        TimeSlot timeSlot = timeSlotMapper.selectById(slotId);
        if (timeSlot == null) {
          throw new BusinessException(ErrorCode.PARAMS_ERROR, "时间段ID无效: " + slotId);
        }

        // 验证地点
        Location location = locationMapper.selectById(locationId);
        if (location == null) {
          throw new BusinessException(ErrorCode.PARAMS_ERROR, "地点ID无效: " + locationId);
        }

        // 创建可用时间配置
        AvailableSlot availableSlot = new AvailableSlot();
        availableSlot.setAdminId(adminId);
        availableSlot.setSlotId(slotId);
        availableSlot.setLocationId(locationId);
        availableSlot.setAssessmentType(assessmentType);
        availableSlot.setAvailable(true);
        availableSlot.setPublishDate(publishDate);
        availableSlot.setPublishYear(year);
        availableSlot.setPublishWeek(week);
        availableSlotMapper.insert(availableSlot);
      }
    }
  }

  @Override
  public Map<String, List<AvailableSlot>> listAvailableSlots(String assessmentType, Integer futureWeeks) {
    // 验证输入
    if (assessmentType == null || (!assessmentType.equals("线上") && !assessmentType.equals("线下"))) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "评估类型格式不正确");
    }

    if (futureWeeks == null || futureWeeks <= 0) {
      futureWeeks = 4; // 默认为4周
    }

    // 获取当前日期及其年份和周数
    LocalDate today = LocalDate.now();
    WeekFields weekFields = WeekFields.of(DayOfWeek.MONDAY, 1);

    Map<String, List<AvailableSlot>> result = new LinkedHashMap<>();
    Map<Integer, List<AvailableSlot>> weekCache = new HashMap<>(); // 缓存已有的周发布

    // 处理当前周和未来几周
    for (int i = 0; i < futureWeeks; i++) {
      LocalDate dateInWeek = today.plusWeeks(i);
      int year = dateInWeek.getYear();
      int week = dateInWeek.get(weekFields.weekOfYear());
      String weekKey = year + "-W" + week;

      // 查询该周的可用时间段
      List<AvailableSlot> availableSlots = availableSlotMapper.selectByYearAndWeek(year, week, assessmentType);

      // 若该周没有发布，则使用上一周的配置（若有）
      if (availableSlots.isEmpty() && i > 0) {
        // 获取上一周的配置
        LocalDate prevWeekDate = dateInWeek.minusWeeks(1);
        int prevYear = prevWeekDate.getYear();
        int prevWeek = prevWeekDate.get(weekFields.weekOfYear());

        List<AvailableSlot> prevWeekSlots;
        if (weekCache.containsKey(prevWeek)) {
          prevWeekSlots = weekCache.get(prevWeek);
        } else {
          prevWeekSlots = availableSlotMapper.selectByYearAndWeek(prevYear, prevWeek, assessmentType);
          weekCache.put(prevWeek, prevWeekSlots);
        }

        if (!prevWeekSlots.isEmpty()) {
          // 复制上一周配置到当前周
          List<AvailableSlot> currentWeekSlots = new ArrayList<>();
          for (AvailableSlot prevSlot : prevWeekSlots) {
            AvailableSlot newSlot = new AvailableSlot();
            BeanUtils.copyProperties(prevSlot, newSlot);
            newSlot.setAsId(null); // 新纪录的ID应为空，由数据库生成
            newSlot.setPublishDate(dateInWeek);
            newSlot.setPublishYear(year);
            newSlot.setPublishWeek(week);
            // 不插入数据库，仅用于展示
            currentWeekSlots.add(newSlot);
          }
          availableSlots = currentWeekSlots;
        }
      }

      // 加载关联的时间段和地点信息
      if (!availableSlots.isEmpty()) {
        loadTimeSlotAndLocationInfo(availableSlots);
        result.put(weekKey, availableSlots);
      } else {
        result.put(weekKey, new ArrayList<>()); // 空列表
      }
    }

    return result;
  }

  /**
   * 加载关联的时间段和地点信息
   */
  private void loadTimeSlotAndLocationInfo(List<AvailableSlot> availableSlots) {
    if (availableSlots == null || availableSlots.isEmpty()) {
      return;
    }

    // 获取所有时间段ID
    List<Integer> slotIds = availableSlots.stream()
        .map(AvailableSlot::getSlotId)
        .distinct()
        .collect(Collectors.toList());

    // 查询时间段信息
    QueryWrapper<TimeSlot> slotQueryWrapper = new QueryWrapper<>();
    slotQueryWrapper.in("slotid", slotIds);
    List<TimeSlot> timeSlots = timeSlotMapper.selectList(slotQueryWrapper);
    Map<Integer, TimeSlot> timeSlotMap = timeSlots.stream()
        .collect(Collectors.toMap(TimeSlot::getSlotId, slot -> slot));

    // 收集所有非空地点ID
    List<Integer> locationIds = availableSlots.stream()
        .map(AvailableSlot::getLocationId)
        .filter(Objects::nonNull)
        .distinct()
        .collect(Collectors.toList());

    Map<Integer, Location> locationMap = new HashMap<>();
    if (!locationIds.isEmpty()) {
      // 查询地点信息
      QueryWrapper<Location> locationQueryWrapper = new QueryWrapper<>();
      locationQueryWrapper.in("locationid", locationIds);
      List<Location> locations = locationMapper.selectList(locationQueryWrapper);
      locationMap = locations.stream()
          .collect(Collectors.toMap(Location::getLocationId, location -> location));
    }

    // 设置关联信息
    for (AvailableSlot slot : availableSlots) {
      slot.setTimeSlot(timeSlotMap.get(slot.getSlotId()));
      if (slot.getLocationId() != null) {
        slot.setLocation(locationMap.get(slot.getLocationId()));
      }
    }
  }
}